﻿#region Using directives
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
#endregion

#region COPYRIGHT

/*
    Copyright (c) 2008, 2009, 2010 
        Roland Rosenkranz (Glatzemann@email.de)
*/

#endregion

#region LICENSE

/*
    This file is part of starLiGHT.Collision.

    starLiGHT.Collision is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    starLiGHT.Collision is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with starLiGHT.Collision.  If not, see <http://www.gnu.org/licenses/>.

    
    ADDITIONAL (commercial) LICENSES for starLiGHT.Collision are available on request.
*/

#endregion

#region Box2D Copyright
/*
* Copyright (c) 2006-2009 Erin Catto http://www.gphysics.com
*
* This software is provided 'as-is', without any express or implied
* warranty.  In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#endregion

#region Version Stuff
// **************[ starLiGHT.Engine SVN ]**********************
// * $Rev:: 1486                  $:  Revision of last commit *
// * $Author:: glatzemann         $:  Author of last commit   *
// * $Date:: 2010-10-08 15:55:47 #$:  Date of last commit     *
// ************************************************************

// **************[ Box2D-Repository Info ]*********************
// Header-File: http://code.google.com/p/box2d/source/browse/trunk/Box2D/Box2D/Dynamics/b2ContactManager.h
// Revision   : r69
// Change-Date: 2010-03-23
//
// Source-File: http://code.google.com/p/box2d/source/browse/trunk/Box2D/Box2D/Dynamics/b2ContactManager.cpp
// Revision   : r109
// Change-Date: 2010-05-31
//
// Status     : Finished!
#endregion

namespace starLiGHT.Physic.TwoD
{
    public class ContactManager
    {
        internal BroadPhase m_broadPhase = new BroadPhase();
        internal Contact m_contactList;
        internal Int32 m_contactCount;
        internal ContactFilter m_contactFilter;
        internal ContactListener m_contactListener;

        public ContactManager()
        {
            m_contactList = null;
            m_contactCount = 0;
            m_contactFilter = new ContactFilter();
            m_contactListener = new ContactListener();
        }

        public void AddPair(Object proxyUserDataA, Object proxyUserDataB)
        {
            FixtureProxy proxyA = (FixtureProxy)proxyUserDataA;
            FixtureProxy proxyB = (FixtureProxy)proxyUserDataB;

            Fixture fixtureA = proxyA.fixture;
            Fixture fixtureB = proxyB.fixture;

            int indexA = proxyA.childIndex;
            int indexB = proxyB.childIndex;
            
            Body bodyA = fixtureA.GetBody();
            Body bodyB = fixtureB.GetBody();
            
            // Are the fixtures on the same body?         
            if (bodyA == bodyB)         
            {
                return;
            }

            if (fixtureA.GetShape().GetType() == ShapeType.FLUID && fixtureB.GetShape().GetType() == ShapeType.FLUID)
            {
                Contact _c = Contact.Create(fixtureA, 0, fixtureB, 0);
                FluidParticle p1 = fixtureA as FluidParticle;
                FluidParticle p2 = fixtureB as FluidParticle;
                p1.addNeighbor(p2);
                p2.addNeighbor(p1);
                return;
            }
            else if (fixtureA.GetShape().GetType() == ShapeType.FLUID || fixtureB.GetShape().GetType() == ShapeType.FLUID)
            {
                Contact _c = Contact.Create(fixtureA, 0, fixtureB, 0);
                fixtureA.m_body.m_world.SPHContacts[_c.GetHashCode()] = _c;
                return;
            }



            // Does a contact already exist?
            ContactEdge edge = bodyB.ContactList;
            while (edge != null)
            {
                if (edge.other == bodyA)
                {
                    Fixture fA = edge.contact.GetFixtureA();
                    Fixture fB = edge.contact.GetFixtureB();
                    int iA = edge.contact.GetChildIndexA();
                    int iB = edge.contact.GetChildIndexB();

                    if (fA == fixtureA && fB == fixtureB && iA == indexA && iB == indexB)
                    {
                        // A contact already exists.
                        return;
                    }
                    
                    if (fA == fixtureB && fB == fixtureA && iA == indexB && iB == indexA)
                    {
                        // A contact already exists.
                        return;
                    }
                }
                
                edge = edge.next;
            }
            
            // Does a joint override collision? Is at least one body dynamic?
            if (bodyB.ShouldCollide(bodyA) == false)
            {
                return;
            }
            
            // Check user filtering.
            if (m_contactFilter != null && m_contactFilter.ShouldCollide(fixtureA, fixtureB) == false)
            {
                return;
            }
            
            // Call the factory.
            Contact c = Contact.Create(fixtureA, indexA, fixtureB, indexB);
            
            // Contact creation may swap fixtures.
            fixtureA = c.GetFixtureA();
            fixtureB = c.GetFixtureB();
            indexA = c.GetChildIndexA();
            indexB = c.GetChildIndexB();
            bodyA = fixtureA.GetBody();
            bodyB = fixtureB.GetBody();
            
            // Insert into the world.
            c.m_prev = null;
            c.m_next = m_contactList;
            if (m_contactList != null)
            {
                m_contactList.m_prev = c;
            }
            m_contactList = c;
            
            // Connect to island graph.
            
            // Connect to body A
            c.m_nodeA.contact = c;
            c.m_nodeA.other = bodyB;          
            
            c.m_nodeA.prev = null;
            c.m_nodeA.next = bodyA.m_contactList;
            if (bodyA.m_contactList != null)
            {
                bodyA.m_contactList.prev = c.m_nodeA;
            }
            bodyA.m_contactList = c.m_nodeA;
            
            // Connect to body B
            c.m_nodeB.contact = c;
            c.m_nodeB.other = bodyA;
            
            c.m_nodeB.prev = null;
            c.m_nodeB.next = bodyB.m_contactList;
            if (bodyB.m_contactList != null)
            {
                bodyB.m_contactList.prev = c.m_nodeB;
            }
            bodyB.m_contactList = c.m_nodeB;
            
            ++m_contactCount; 
        }

        public void FindNewContacts()
        {
            m_broadPhase.UpdatePairs(this);
        }

	    public void Destroy(Contact c)
        {
            Fixture fixtureA = c.GetFixtureA();
            Fixture fixtureB = c.GetFixtureB();
            Body bodyA = fixtureA.GetBody();
            Body bodyB = fixtureB.GetBody();

            if (fixtureA.GetShape().GetType() == ShapeType.FLUID && fixtureB.GetShape().GetType() == ShapeType.FLUID)
            {
                FluidParticle p1 = fixtureA as FluidParticle;
                FluidParticle p2 = fixtureB as FluidParticle;
                p1.removeNeighbor(p2);
                p2.removeNeighbor(p1);
                Contact.Destroy(ref c);
                return;
            }
            else if (fixtureA.GetShape().GetType() == ShapeType.FLUID || fixtureB.GetShape().GetType() == ShapeType.FLUID)
            {
                int key = c.GetHashCode();
                fixtureA.m_body.m_world.SPHContacts.Remove(key);
                Contact.Destroy(ref c);
                return;
            }

            if (m_contactListener != null && c.IsTouching())
            {
                m_contactListener.EndContact(c);
            }
            
            // Remove from the world.
            if (c.m_prev != null)
            {
                c.m_prev.m_next = c.m_next;
            }
            
            if (c.m_next != null)
            {
                c.m_next.m_prev = c.m_prev;
            }
            
            if (c == m_contactList)
            {
                m_contactList = c.m_next;
            }
            
            // Remove from body 1
            if (c.m_nodeA.prev != null)
            {
                c.m_nodeA.prev.next = c.m_nodeA.next;
            }
            
            if (c.m_nodeA.next != null)
            {
                c.m_nodeA.next.prev = c.m_nodeA.prev;
            }
            
            if (c.m_nodeA == bodyA.m_contactList)
            {
                bodyA.m_contactList = c.m_nodeA.next;
            }
            
            // Remove from body 2
            if (c.m_nodeB.prev != null)
            {
                c.m_nodeB.prev.next = c.m_nodeB.next;
            }
            
            if (c.m_nodeB.next != null)
            {
                c.m_nodeB.next.prev = c.m_nodeB.prev;
            }
            
            if (c.m_nodeB == bodyB.m_contactList)
            {
                bodyB.m_contactList = c.m_nodeB.next;
            }
            
            // Call the factory.
            Contact.Destroy(ref c);
            --m_contactCount;
        }

        /// <summary>This is the top level collision call for the time step. Here
        /// all the narrow phase collision is processed for the world
        /// contact list.</summary>
        public void Collide()
        {
            // Update awake contacts.
            Contact c = m_contactList;
            while (c != null)
            {
                Fixture fixtureA = c.GetFixtureA();
                Fixture fixtureB = c.GetFixtureB();
                int indexA = c.GetChildIndexA();
                int indexB = c.GetChildIndexB();
                Body bodyA = fixtureA.GetBody();
                Body bodyB = fixtureB.GetBody();

                if (bodyA.Awake == false && bodyB.Awake == false)
                {
                    c = c.GetNext();
                    continue;
                }

                // Is this contact flagged for filtering?
                if ((c.m_flags & Contact.ContactFlags.e_filterFlag) == Contact.ContactFlags.e_filterFlag)
                {
                    // should these bodies collide?
                    if (bodyB.ShouldCollide(bodyA) == false)
                    {
                        Contact cNuke = c;
                        c = cNuke.GetNext();
                        Destroy(cNuke);
                        continue;
                    }

                    // Check user filtering.
                    if (m_contactFilter != null && m_contactFilter.ShouldCollide(fixtureA, fixtureB) == false)
                    {
                        Contact cNuke = c;
                        c = cNuke.GetNext();
                        Destroy(cNuke);
                        continue;
                    }

                    // Clear the filtering flag.
                    c.m_flags &= ~Contact.ContactFlags.e_filterFlag;
                }

                Int32 proxyIdA = fixtureA.m_proxies[indexA].proxyId;
                Int32 proxyIdB = fixtureB.m_proxies[indexB].proxyId;
                bool overlap = m_broadPhase.TestOverlap(proxyIdA, proxyIdB);

                // Here we destroy contacts that cease to overlap in the broad-phase.
                if (overlap == false)
                {
                    Contact cNuke = c;
                    c = cNuke.GetNext();
                    Destroy(cNuke);
                    continue;
                }

                // The contact persists.
                c.Update(m_contactListener);
                c = c.GetNext();
            }
        }

        public BroadPhase BroadPhase
        {
            get { return m_broadPhase; }
        }

    };
}
